package com.example.framework.web.core.handler;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.example.framework.common.exception.ServiceException;
import com.example.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.example.framework.common.pojo.R;
import com.example.framework.web.core.util.WebFrameworkUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.event.Level;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.util.Map;
import java.util.stream.Collectors;

/**
 * 全局异常处理器，将 Exception 翻译成 R + 对应的异常编号
 */
@RestControllerAdvice
@AllArgsConstructor
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 处理所有异常，主要是提供给 Filter 使用
     * 因为 Filter 不走 SpringMVC 的流程，但是我们又需要兜底处理异常，所以这里提供一个全量的异常处理过程，保持逻辑统一。
     *
     * @param ex 异常
     * @return 通用返回
     */
    public R<?> allExceptionHandler(Throwable ex) {
        if (ex instanceof MissingServletRequestParameterException) {
            return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
        }
        if (ex instanceof MethodArgumentTypeMismatchException) {
            return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);
        }
        if (ex instanceof BindException || ex instanceof ConstraintViolationException) {
            return handleMethodArgumentNotValidException((Exception) ex);
        }
        if (ex instanceof ValidationException) {
            return validationException((ValidationException) ex);
        }
        if (ex instanceof NoHandlerFoundException) {
            return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex);
        }
        if (ex instanceof NoResourceFoundException) {
            return noResourceFoundExceptionHandler((NoResourceFoundException) ex);
        }
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
        }
        if (ex instanceof ServiceException) {
            return serviceExceptionHandler((ServiceException) ex);
        }
        return defaultExceptionHandler(ex);
    }

    /**
     * 处理 SpringMVC 请求参数缺失
     * <p>
     * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public R<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        log.error("[missingServletRequestParameterExceptionHandler]", ex);
        return R.fail(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     * <p>
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
        log.error("[missingServletRequestParameterExceptionHandler]", ex);
        return R.fail(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
    }

    @ExceptionHandler({BindException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class})
    public R<Object> handleMethodArgumentNotValidException(Exception exception) {
        String message = null;
        if (exception instanceof BindException bindException) {
            // 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
            Map<String, String> errorMap = bindException.getFieldErrors()
                    .stream()
                    .filter(e -> StrUtil.isNotBlank(e.getDefaultMessage()))
                    .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
            message = errorMap.toString();
        } else if (exception instanceof ConstraintViolationException constraintViolationException) {
            // 处理 SpringMVC 参数校验不正确，Controller层的请求参数
            message = constraintViolationException.getMessage();
            // 等同于下面的遍历
//            for (ConstraintViolation<?> constraintViolation : constraintViolationException.getConstraintViolations()) {
//                String param = constraintViolation.getPropertyPath().toString();
//                String errorMessage = constraintViolation.getMessage();
//            }
        }
        log.error("参数绑定异常，{} ", message);
        return R.fail(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), String.format("请求参数不正确：%s", message));
    }

    /**
     * 处理 Dubbo Consumer 本地参数校验时，抛出的 ValidationException 异常
     */
    @ExceptionHandler(value = ValidationException.class)
    public R<?> validationException(ValidationException ex) {
        log.error("[constraintViolationExceptionHandler]", ex);
        // 无法拼接明细的错误信息，因为 Dubbo Consumer 抛出 ValidationException 异常时，是直接的字符串信息，且人类不可读
        return R.fail(GlobalErrorCodeConstants.BAD_REQUEST);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R<String> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException ex) {
        log.error("[httpMessageNotReadableExceptionHandler], e: {}", ex.getMessage());
        return R.fail(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), String.format("请求参数不正确：%s", ex.getMessage()));
    }

    @ExceptionHandler(value = IllegalArgumentException.class)
    public R<?> illegalArgumentExceptionHandler(IllegalArgumentException ex) {
        log.error("[illegalArgumentExceptionHandler], e: {}", ex.getMessage());
        return R.fail(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), String.format("请求参数不正确：%s", ex.getMessage()));
    }

    /**
     * 处理 SpringMVC 请求地址不存在
     * <p>
     * 注意，它需要设置如下两个配置项：
     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true
     * 2. spring.mvc.static-path-pattern 为 /statics/**
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public R<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
        log.warn("[noHandlerFoundExceptionHandler]", ex);
        return R.fail(GlobalErrorCodeConstants.NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
    }

    /**
     * 处理 SpringMVC 请求方法不正确
     * <p>
     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
        log.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
        return R.fail(GlobalErrorCodeConstants.METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
    }

    @ResponseStatus(HttpStatus.NOT_MODIFIED)
    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public String asyncRequestTimeoutHandler(AsyncRequestTimeoutException e) {
        log.error("[异步请求超时]", e);
        return "304";
    }

    @ExceptionHandler(NoResourceFoundException.class)
    public R<?> noResourceFoundExceptionHandler(NoResourceFoundException ex) {
        log.warn("[noResourceFoundExceptionHandler], e: {}", ex.getMessage());
        return R.fail(GlobalErrorCodeConstants.NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getResourcePath()));
    }

    /**
     * 处理 Spring Security 权限不足的异常
     * <p>
     * 来源是，使用 @PreAuthorize 注解，AOP 进行权限拦截
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    // 抛出 AccessDeniedException 不是看 UserDetails.getAuthorities() 而是 authenticate.getAuthorities()
    public R<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
        log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", WebFrameworkUtils.getLoginUserId(req),
                req.getRequestURL(), ex);
        return R.fail(GlobalErrorCodeConstants.FORBIDDEN.getCode(), GlobalErrorCodeConstants.FORBIDDEN.getMsg());
    }

    /**
     * 处理业务异常 ServiceException
     * <p>
     * 例如说，商品库存不足，用户手机号已存在。
     */
    @ExceptionHandler(value = ServiceException.class)
    public R<?> serviceExceptionHandler(ServiceException ex) {
        Level logLevel = ex.getLogLevel();
        if (logLevel == Level.INFO) {
            log.info("[serviceExceptionHandler] {}", ExceptionUtil.getMessage(ex));
        } else {
            log.atLevel(logLevel).log("[serviceExceptionHandler] {}", ex);
        }
        return R.fail(ex.getCode(), ex.getMessage());
    }

    /**
     * 处理系统异常，兜底处理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public R<?> defaultExceptionHandler(Throwable ex) {
        log.error("[defaultExceptionHandler]", ex);
        // 返回 ERROR R
        return R.fail(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(), GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getMsg());
    }

}
